All files / util obj_map.ts

100% Statements 52/52
100% Branches 14/14
100% Functions 9/9
100% Lines 45/45
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111                                  2x                   2x             1670x       1670x     2x 3440x 3440x 3440x 1067x   2381x 2381x 2359x     14x     2x 421x       2x 1457x 1457x 1457x 1231x 1231x   226x 227x 219x 219x     7x           2x 718x 718x 718x 2x   716x 718x 715x 708x   7x   715x     1x     2x 3137x 2068x 2068x         2x 46x   2x  
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { Equatable } from './misc';
import * as objUtil from './obj';
 
type Entry<K, V> = [K, V];
 
/**
 * A map implementation that uses objects as keys. Objects must implement the
 * Equatable interface and must be immutable. Entries in the map are stored
 * together with the key being produced from the mapKeyFn. This map
 * automatically handles collisions of keys.
 */
export class ObjectMap<KeyType extends Equatable<KeyType>, ValueType> {
  /**
   * The inner map for a key -> value pair. Due to the possibility of
   * collisions we keep a list of entries that we do a linear search through
   * to find an actual match. Note that collisions should be rare, so we still
   * expect near constant time lookups in practice.
   */
  private inner: {
    [canonicalId: string]: Array<Entry<KeyType, ValueType>>;
  } = {};
 
  constructor(private mapKeyFn: (key: KeyType) => string) {}
 
  /** Get a value for this key, or undefined if it does not exist. */
  get(key: KeyType): ValueType | undefined {
    const id = this.mapKeyFn(key);
    const matches = this.inner[id];
    if (matches === undefined) {
      return undefined;
    }
    for (const [otherKey, value] of matches) {
      if (otherKey.isEqual(key)) {
        return value;
      }
    }
    return undefined;
  }
 
  has(key: KeyType): boolean {
    return this.get(key) !== undefined;
  }
 
  /** Put this key and value in the map. */
  set(key: KeyType, value: ValueType): void {
    const id = this.mapKeyFn(key);
    const matches = this.inner[id];
    if (matches === undefined) {
      this.inner[id] = [[key, value]];
      return;
    }
    for (let i = 0; i < matches.length; i++) {
      if (matches[i][0].isEqual(key)) {
        matches[i] = [key, value];
        return;
      }
    }
    matches.push([key, value]);
  }
 
  /**
   * Remove this key from the map. Returns a boolean if anything was deleted.
   */
  delete(key: KeyType): boolean {
    const id = this.mapKeyFn(key);
    const matches = this.inner[id];
    if (matches === undefined) {
      return false;
    }
    for (let i = 0; i < matches.length; i++) {
      if (matches[i][0].isEqual(key)) {
        if (matches.length === 1) {
          delete this.inner[id];
        } else {
          matches.splice(i, 1);
        }
        return true;
      }
    }
    return false;
  }
 
  forEach(fn: (key: KeyType, val: ValueType) => void): void {
    objUtil.forEach(this.inner, (_, entries) => {
      for (const [k, v] of entries) {
        fn(k, v);
      }
    });
  }
 
  isEmpty(): boolean {
    return objUtil.isEmpty(this.inner);
  }
}